[!NOTE]
本系列文章已進行全域去敏與匿名化處理,專案代號統一為DataGateway,內網 IP 統一為10.0.0.x。
回顧前幾天的進度,我們利用 usb_disk.img 作為共用空間,一端透過 USB ConfigFS 餵給 Windows PC,另一端透過 losetup 掛載在 Raspberry Pi 上。
從系統架構圖來看,這似乎非常完美。硬體端的資料送進 DataGateway,寫入虛擬磁碟後,插著 USB 的那台 Windows PC 就能立刻把 .csv 開起來看。
但如果你真的這麼做,你會發現一場災難。檔案會憑空消失、磁區會變成亂碼、甚至 Windows 會跳出「磁碟結構毀損且無法讀取」的警告。
歡迎來到分散式與儲存系統中最經典的惡夢:「雙腦分裂 (Split-Brain)」。
FAT32 是一種非常古老、簡單且假設單一使用者的檔案系統。它依賴檔案分配表 (File Allocation Table) 來記錄哪個檔案放在硬碟的哪個磁區。
當你在 Linux (Raspberry Pi) 或 Windows 系統中掛載一個 FAT32 磁碟時,作業系統會做一件事:快取 (Cache) 檔案分配表。
作業系統為了效能,會把檔案目錄樹與可用空間記錄讀進 RAM 裡,然後盡可能都在記憶體中操作,等到必要時(或者使用者點擊「安全移除硬體」時)才寫回實體硬碟。
現在我們的情境是:
usb_disk.img 掛載起來,讀取了它認為的 FAT 表。losetup 把 usb_disk.img 掛載起來,也讀取了另一份 FAT 表。DataGateway 的感測器收到了新資料,Linux 決定寫入一份 patient_123.csv。Linux 修改了它自己 RAM 裡的 FAT 表,把資料寫進硬碟的某個磁區 A。note.txt。Windows 依照它自己的快取,認為磁區 A 是空的,於是直接把 note.txt 寫進磁區 A。轟!
Windows 直接覆蓋了 Linux 剛剛寫入的二進位資料。
而且因為 Linux 完全不知道 Windows 修改了硬碟,Linux 接下來若要讀取 patient_123.csv,讀出來的就會是 Windows 寫進去的 note.txt 亂碼,最終導致 FAT 表完全崩潰。
[!CAUTION]
絕對禁止多個作業系統同時掛載同一個 Block Device (除非使用特製的 Cluster File System,如 OCFS2 或 GFS2,但 Windows 都不支援)。這會導致不可逆的資料毀損!
為什麼一般的隨身碟不會發生這種事?
因為你把實體隨身碟插到電腦 A,再拔下來插到電腦 B。這是一個互斥 (Mutually Exclusive) 的操作。
當隨身碟在空中時,不會有任何作業系統對它進行寫入,因此每次掛載,OS 讀取到的 FAT 表永遠是最新的。
但在我們的 DataGateway 架構中,這顆 usb_disk.img 是虛擬的,它可以無時無刻被兩個作業系統透過不同路徑存取,導致我們失去了硬體層面上的互斥鎖 (Hardware Mutex)。
為了解決這個致命的架構缺陷,我們在開發初期嘗試了幾條路:
有工程師提議,只要 Linux 在寫入後立刻執行 sync 指令強制作業系統將緩存刷入硬碟,Windows 端不就能看到新資料了嗎?
失敗。 雖然 Linux 把資料寫下去了,但 Windows 不會知道。Windows 依然相信它在插入隨身碟那一刻快取在 RAM 裡的 FAT 結構。除非你把 USB 拔掉重插,否則 Windows 永遠看不到新檔案。
既然同時寫入會毀滅世界,那我們用 ConfigFS 把 USB 設為唯讀 (echo 1 > lun.0/ro),只有 Linux 能寫,Windows 只能讀。這樣總行了吧?
失敗。 就算 Windows 是唯讀,它依然會有快取問題。Linux 寫入的新資料,Windows 不拔掉重插一樣看不見。更慘的是,某些醫療軟體在讀取資料庫時,會習慣在隨身碟裡產生 .tmp 或 .lock 鎖定檔,如果 USB 是唯讀的,這些軟體會直接報錯崩潰。
我們必須用軟體機制,還原「隨身碟被拔下」的這個物理動作。
核心概念只有一句話:
同一時間,usb_disk.img 只能被其中一方掛載。
這就是我們實作的「智慧卸載」機制。
這聽起來很直觀,但在沒有實體按鈕的無頭設備 (Headless) 上,我們怎麼知道 USB 線被插上了?而且,如果在插上的那一瞬間,Linux 剛好正在寫入資料,直接卸載會不會導致資料遺失?
明天,我們將實作一套精密的 USB 狀態監聽機制,在千分之一秒內完成磁碟控制權的奪取與移交!